#pragma once
#include "comm.hpp"
#include "InetAddr.hpp"


//using task_t = std::function<void()>;
using func_t = std::function<std::string(const std::string&, InetAddr&)>;
using namespace ThreadPoolModule;

class Tcpsocket : public Nocopy
{
public:
    Tcpsocket(uint16_t port, func_t func)
        : _port(port), _isrunning(false), _func(func)
    {}

    ~Tcpsocket(){}

    void Init()
    {
        // 1.创建tcp socket
        int n = socket(AF_INET, SOCK_STREAM, 0);
        if(n < 0){
            LOG(loglevel::FATAL) << "create tcp socket error";
            exit(SOCKET_ERR);
        }
        _listensockfd = n;
        LOG(loglevel::INFO) << "create tcp socket success->" << _listensockfd;

        // 2.绑定套接字
        InetAddr addr(_port);
        n = bind(_listensockfd, CONVERT(addr), sizeof(addr));
        if(n < 0){
            LOG(loglevel::FATAL) << "bind tcp socket error";
            exit(BIND_ERR);
        }
        LOG(loglevel::INFO) << "bind tcp socket success->" << _listensockfd;

        // 3.监听
        // tcp套接字绑定之外，要将自己的状态设置为监听状态，只要将状态设置为监听状态，外界就可以连接
        n = listen(_listensockfd, 8);
        if(n < 0){
            LOG(loglevel::FATAL) << "bind tcp socket error";
            exit(LISTEN_ERR);
        }
        LOG(loglevel::INFO) << "listen tcp socket success->" << _listensockfd;

    }

    void Run()
    {
        _isrunning = true;
        while(_isrunning){
            // 1. 获取连接
            sockaddr_in client;
            socklen_t client_size = sizeof(client);
            int sockfd = accept(_listensockfd, CONVERT(client), &client_size);
            if(sockfd < 0){
                LOG(loglevel::WARINING) << "accept error";
            }
            LOG(loglevel::INFO) << "accept success" << "sockfd->" << sockfd;
            InetAddr cli(client);

            ThreadData *td = new ThreadData(sockfd, cli, this);
            pthread_t tid = 0;
            pthread_create(&tid, nullptr, routine, td);

            // version0 --- 单线程版，服务器只有一个线程，当服务器运行，此时主线程就会跳转去执行service，导致无法处理其他的连接
            //Service(sockfd, cli);

            // version1 --- 多线程版，借助父子进程，让子进程去执行service方法，父进程继续去接收连接
            //int id = fork();
            //if(id == 0){
            //    // 子进程
            //    close(_listensockfd); // 子进程不需要访问listensockfd
            //    if(fork() > 0){
            //        exit(OK);
            //    }
            //    // 走到这里是孙子进程
            //    Service(sockfd, cli);
            //    exit(OK);
            //}
            //else if(id > 0){
            //    // 父进程
            //    // 按道理来说，父进程必须等待子进程，而且服务器是一种常驻内存的应用，不回收子进程会导致内存泄露问题，这对常驻内存的程序是致命的
            //    // 解决方法1：子进程结束会给父进程发送SIGCHLD信号，我们在初始化服务器的时候，对该信号进行忽略捕捉，此时子进程就会自动退出，不需要回收其资源
            //    // 解决方法2：waitpid非阻塞等待，但是并不推荐，如果父进程在accept处阻塞了，就会导致子进程无法被回收
            //    // 解决方法3：创建孙子进程，子进程直接退出，父进程直接阻塞等待，让孙子进程去service
            //    //           因为子进程直接退出了，所以父进程不会阻塞，而孙子进程称为孤儿，会被1号进程领养，不会内存泄露
            //    close(sockfd);
            //    waitpid(id, nullptr, 0);
            //}
            //else{
            //    // 服务器fork子进程都失败了，说明此时服务器高负荷了，就别再玩了
            //    LOG(loglevel::FATAL) << "fork error";
            //    exit(FORK_ERR);
            //}

            // version2 --- 多线程版, 线程也需要被join，所以我们避免主线程join新线程，这里我们detach新线程
            // 在类内编写新线程的入口函数时，需要注意this指针，所以我们要将其声明为static
            //ThreadData *td = new ThreadData(sockfd, cli, this);
            //pthread_t tid = 0;
            //pthread_create(&tid, nullptr, routine, td);

            // version3 --- 线程池版
            //ThreadPool<task_t>::Getinstance()->emplace([this, &sockfd, &cli](){
            //    Service(sockfd, cli);
            //});
        }
    }

    struct ThreadData
    {
        ThreadData(int sd, InetAddr ad, Tcpsocket *tcp)
            :sockfd(sd), addr(ad), self(tcp)
        {}
        int sockfd;
        InetAddr addr;
        Tcpsocket *self;
    };

    static void *routine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData*>(args);
        td->self->Service(td->sockfd, td->addr);
        delete td;
        return nullptr;
    }

    // 这里的service是长服务，即启动后一直不退出，除非用户手动退出
    // 长服务就比较适合使用多线程和多进程来实现
    // 短服务:收到一次消息，处理一次，短服务，就不适合借助多线程和多进程来实现
    // 这样就需要频繁创建进程和线程，消耗是非常大的
    // 所以短服务，我们可以借助线程池来实现
    void Service(int sockfd, InetAddr &cli)
    {
        // 1.收信息
        char buffer[1024];
        while(true){
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if(n > 0){
               buffer[n] = 0;
               LOG(loglevel::INFO) << "client say#" << buffer; 
            
               // 2.写回数据
               //std::string echostring = "echo#";
               //echostring += buffer;

               std::string echostring = _func(buffer, cli);
               write(sockfd, echostring.c_str(), echostring.size());
            }
            else if(n == 0){
                // read返回值为0，表示对端把连接关了
                LOG(loglevel::FATAL) << "client quit";
                close(sockfd);
                break;
            }
            else{
                // read返回值小于0，表示读取失败
                LOG(loglevel::FATAL) << "read error";
                close(sockfd);
                break;
            }
        }
    }
private:
    uint16_t _port; // 端口号
    int _listensockfd; // 监听
    bool _isrunning;

    func_t _func;
};
